﻿//------------------------------------------------------------------------------
// <copyright file="MainWindow.xaml.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

namespace Microsoft.Samples.Kinect.DepthBasics
{
    using System;
    using System.Drawing;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using Microsoft.Kinect;
    using AForge.Video.VFW;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using System.Runtime.InteropServices;
    using AForge.Video;
    using AForge;
    using AForge.Video.FFMPEG;
    using System.Collections.Generic;
    using System.Drawing.Imaging;
    using System.Linq;
    /// <summary>
    /// Interaction logic for MainWindow
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        /// <summary>
        /// Map depth range to byte range
        /// </summary>
        private const int MapDepthToByte = 8000 / 256;
        
        /// <summary>
        /// Active Kinect sensor
        /// </summary>
        private KinectSensor kinectSensor = null;

        /// <summary>
        /// Reader for depth frames
        /// </summary>
        private DepthFrameReader depthFrameReader = null;

        /// <summary>
        /// Reader for color frames
        /// </summary>
        private ColorFrameReader colorFrameReader = null;

        /// <summary>
        /// Description of the data contained in the depth frame
        /// </summary>
        private FrameDescription depthFrameDescription = null;

        /// <summary>
        /// Description of the data contained in the color frame
        /// </summary>
        private FrameDescription colorFrameDescription = null;
            
        /// <summary>
        /// Bitmap to display
        /// </summary>
        private WriteableBitmap depthBitmap = null;

        /// <summary>
        /// colorBitmap to display
        /// </summary>
        private WriteableBitmap colorBitmap = null;

        /// <summary>
        /// Intermediate storage for frame data converted to color
        /// </summary>
        private byte[] depthPixels = null;

        /// <summary>
        /// Array of color pixel
        /// </summary>
        private byte[] colorPixels = null;

        /// <summary>
        /// Current status text to display
        /// </summary>
        private string statusText1 = null;

        private string statusText2 = null;

        /// <summary>
        /// 文件数量
        /// </summary>
        private int framenamecnt = 0;

        private double fps = 0;

        private string statusText3="0";

        private DateTime oldTime;

        private DateTime nowTime;

        private double s;


        struct savecolorpara
        {
            public byte[] data;
            public int width;
            public int height;
            public int stride;
            public string folder;
            public string filename;
        }

        //端口号、IP地址
        static int port = 8000;
        static string host = "169.254.233.99";
        //static string host = "127.0.0.1";
        static IPAddress ip = IPAddress.Parse(host);
        static IPEndPoint ipe = new IPEndPoint(IPAddress.Any, port);        //IPEndPoint类包含应用程序连接到主机上的服务所需的主机
                                                                            //和端口信息，通过组合服务的主机IP地址和端口号，
                                                                            //IPEndPoint类形成到服务的连接点。
        static EndPoint ep = (EndPoint)ipe; //绑定到 8000 端口
        //创建一个Socket类
        static Socket sclient = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        /*Dgram支持数据报，即为固定 （通常很小） 的最大长度的无连接的、 不可靠的消息。 消息可能会丢失或重复，并且可能
         * 不按顺序抵达。 一个 Socket 类型的 Dgram 不需要任何连接之前发送和接收数据，并且可以与多个对等方通信。 
         * Dgram 使用数据报协议 (Udp) 和 InterNetworkAddressFamily。*/
        //	InterNetWork IP 版本 4 地址。

        static bool key = false;

        private void socket_client_threadfun()
        {
            while (true)
            {
                byte[] msg = new byte[2];
                sclient.Receive(msg);
                sclient.ReceiveFrom(msg, ref ep);                   //接受来自服务器的数据
                string msgs = Encoding.Unicode.GetString(msg);      //Unicode編碼方式,把msgs轉成string
                if (msgs.Equals("s"))
                    key = true;
                if (msg.Equals("e"))
                {
                    key = false;
                }

            }
            //Thread socketthread = new Thread(socket_client_threadfun);
            //socketthread.Start();
        }

        /// <summary>
        /// Initializes a new instance of the MainWindow class.
        /// </summary>
        public MainWindow()
        {
            // get the kinectSensor object
            this.kinectSensor = KinectSensor.GetDefault();

            // open the reader for the depth frames
            this.depthFrameReader = this.kinectSensor.DepthFrameSource.OpenReader();

            //open the reader for the color frames
            this.colorFrameReader = this.kinectSensor.ColorFrameSource.OpenReader();

            // wire handler for frame arrival
            this.depthFrameReader.FrameArrived += this.Reader_depthFrameArrived;

            this.colorFrameReader.FrameArrived += this.Reader_colorFrameArrived;
          
            // get FrameDescription from DepthFrameSource
            this.depthFrameDescription = this.kinectSensor.DepthFrameSource.FrameDescription;

            this.colorFrameDescription = this.kinectSensor.ColorFrameSource.CreateFrameDescription(ColorImageFormat.Bgra);

            // allocate space to put the pixels being received and converted
            this.depthPixels = new byte[this.depthFrameDescription.Width * this.depthFrameDescription.Height];

            this.colorPixels = new byte[this.colorFrameDescription.Width*this.colorFrameDescription.Height*4];


            // create the bitmap to display
            this.depthBitmap = new WriteableBitmap(this.depthFrameDescription.Width, this.depthFrameDescription.Height, 96.0, 96.0, PixelFormats.Gray8, null);

            this.colorBitmap = new WriteableBitmap(this.colorFrameDescription.Width,this.colorFrameDescription.Height,96.0,96.0,PixelFormats.Bgr32,null);

            // set IsAvailableChanged event notifier
            this.kinectSensor.IsAvailableChanged += this.Sensor_IsAvailableChanged;

            sclient.Bind(ipe);//套接字与本地地址绑定bind函数

            Thread socketthread = new Thread(socket_client_threadfun);
            socketthread.Start();
            while (key == false) { }
            // open the sensor
            if (key == true)
            {
                this.kinectSensor.Open();
            }


            // set the status text
            this.StatusText1 = this.kinectSensor.IsAvailable ? Properties.Resources.RunningStatusText
                                                            : Properties.Resources.NoSensorStatusText;
            this.StatusText2 = this.kinectSensor.IsAvailable ? Properties.Resources.RunningStatusText
                                                            : Properties.Resources.NoSensorStatusText;

            // use the window object as the view model in this simple example
            this.DataContext = this;

            // initialize the components (controls) of the window
            this.InitializeComponent();
        }

        /// <summary>
        /// INotifyPropertyChangedPropertyChanged event to allow window controls to bind to changeable data
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Gets the bitmap to display
        /// </summary>
        public ImageSource ImageSource1
        {

            get
            {
                return this.depthBitmap;
            }
        }
        public ImageSource ImageSource2
        {

            get
            {
                return this.colorBitmap;
            }
        }


        /// <summary>
        /// Gets or sets the current status text to display
        /// </summary>
        public string StatusText1
        {
            get
            {
                return this.statusText1;
            }

            set
            {
                if (this.statusText1 != value)
                {
                    this.statusText1 = value;

                    // notify any bound elements that the text has changed
                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs("StatusText1"));
                    }
                }
            }
        }

        public string StatusText2
        {
            get
            {
                return this.statusText2;
            }

            set
            {
                if (this.statusText2 != value)
                {
                    this.statusText2 = value;

                    // notify any bound elements that the text has changed
                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs("StatusText2"));
                    }
                }
            }
        }

        public string StatusText3
        {
            get
            {
                return this.statusText3;
            }
            set
            {
                if (this.statusText3 != value)
                {
                    this.statusText3 = value;

                    // notify any bound elements that the text has changed
                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs("StatusText3"));
                    }
                }
            }
        }


        /// <summary>
        /// Execute shutdown tasks
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void MainWindow_Closing(object sender, CancelEventArgs e)
        {
            if (this.depthFrameReader != null)
            {
                // DepthFrameReader is IDisposable
                this.depthFrameReader.Dispose();
                this.depthFrameReader = null;
            }

            if (this.colorFrameReader != null)
            {
                // ColorFrameReader is IDisposable
                this.colorFrameReader.Dispose();
                this.colorFrameReader = null;
            }

            if (this.kinectSensor != null)
            {
                this.kinectSensor.Close();
                this.kinectSensor = null;
            }
        }

        /// <summary>
        /// Handles the depth frame data arriving from the sensor
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void Reader_depthFrameArrived(object sender, DepthFrameArrivedEventArgs e)
        {
            bool depthFrameProcessed = false;

            using (DepthFrame depthFrame = e.FrameReference.AcquireFrame())
            {
                if (depthFrame != null)
                {
                    // the fastest way to process the body index data is to directly access 
                    // the underlying buffer
                    using (Microsoft.Kinect.KinectBuffer depthBuffer = depthFrame.LockImageBuffer())
                    {
                        // verify data and write the color data to the display bitmap
                        if (((this.depthFrameDescription.Width * this.depthFrameDescription.Height) == (depthBuffer.Size / this.depthFrameDescription.BytesPerPixel)) &&
                            (this.depthFrameDescription.Width == this.depthBitmap.PixelWidth) && (this.depthFrameDescription.Height == this.depthBitmap.PixelHeight))
                        {
                            // Note: In order to see the full range of depth (including the less reliable far field depth)
                            // we are setting maxDepth to the extreme potential depth threshold
                            ushort maxDepth = ushort.MaxValue;

                            // If you wish to filter by reliable depth distance, uncomment the following line:
                            //// maxDepth = depthFrame.DepthMaxReliableDistance
                            
                            this.ProcessDepthFrameData(depthBuffer.UnderlyingBuffer, depthBuffer.Size, depthFrame.DepthMinReliableDistance, maxDepth);
                            depthFrameProcessed = true;
                        }
                    }
                }
            }

            if (depthFrameProcessed)
            {
                this.RenderDepthPixels();
            }
        }

        /// <summary>
        /// Handles the color frame data arriving from the sensor
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void Reader_colorFrameArrived(object sender, ColorFrameArrivedEventArgs e)
        {
            using (ColorFrame colorFrame = e.FrameReference.AcquireFrame())
            {
                // Check if width/height matches
                if (this.colorFrameDescription.Width == colorBitmap.PixelWidth && this.colorFrameDescription.Height == colorBitmap.PixelHeight&&colorFrame != null)
                {
                    savecolorpara scp;

                    using (KinectBuffer colorBuffer = colorFrame.LockRawImageBuffer())
                    {
                        this.colorBitmap.Lock();
                        if ((this.colorFrameDescription.Width == this.colorBitmap.PixelWidth) && (this.colorFrameDescription.Height == this.colorBitmap.Height))
                        {
                            colorFrame.CopyConvertedFrameDataToIntPtr(
                                this.colorBitmap.BackBuffer,
                                (uint)(this.colorFrameDescription.Width * this.colorFrameDescription.Height * 4),
                                ColorImageFormat.Bgra);
                            this.colorBitmap.AddDirtyRect(new Int32Rect(0, 0, this.colorBitmap.PixelWidth, this.colorBitmap.PixelHeight));
                        }
                        this.colorBitmap.Unlock();
                    }

                    scp.data = colorPixels;
                    scp.width = this.colorFrameDescription.Width;
                    scp.height = this.colorFrameDescription.Height;
                    scp.stride = this.colorFrameDescription.Width * 4;
                    scp.folder = "F:\\KinectData\\color\\";
                    scp.filename = framenamecnt.ToString("D5");// +"-" + t.Minute.ToString() + "-" + t.Second.ToString() + "-" + t.Millisecond.ToString();
                                                                    // scp.filename = framenamecnt.ToString("D5") + "-" + colorFrame.RelativeTime.Minutes.ToString() + "-" + colorFrame.RelativeTime.Seconds.ToString() + "-" + colorFrame.RelativeTime.Milliseconds.ToString();
                                                                    //scp.filename = colorFrame.RelativeTime.Minutes.ToString() + "-" + colorFrame.RelativeTime.Seconds.ToString() + "-" + colorFrame.RelativeTime.Milliseconds.ToString();
                                                                    // Copy data to array based on image format
                    if (colorFrame.RawColorImageFormat == ColorImageFormat.Bgra)
                    {
                        colorFrame.CopyRawFrameDataToArray(colorPixels);
                        Thread savecolorthread = new Thread(savecolorthreadfun);
                        savecolorthread.Start((object)scp);
                    }
                    else
                    {
                        colorFrame.CopyConvertedFrameDataToArray(colorPixels, ColorImageFormat.Bgra);
                        Thread savecolorthread = new Thread(savecolorthreadfun);
                        savecolorthread.Start((object)scp);
                    }

                }
            }
        }

        private void savecolorthreadfun(object o)
        {
            if(framenamecnt%50==0)
            {
                oldTime = DateTime.Now;
            }
            savecolorpara scp = (savecolorpara)o;
            if (scp.data != null)
            {
                /*
                WriteableBitmap bmp = new WriteableBitmap(scp.width, scp.height, 96.0, 96.0, PixelFormats.Bgr32, null);

                // write pixels to bitmap 更改点阵图的指定区域中的像素，矩阵的WriteBitmap更新，用来更新点阵图的像素阵列，更新区域的stride pixels，输入缓冲区域的位移
                bmp.WritePixels(new Int32Rect(0, 0, scp.width, scp.height), scp.data, scp.stride, 0);

                // create jpg encoder from bitmap
                JpegBitmapEncoder enc = new JpegBitmapEncoder();

                // create frame from the writable bitmap and add to encoder
                enc.Frames.Add(BitmapFrame.Create(bmp));

                // Create whole path
                string path = Path.Combine(scp.folder, scp.filename + ".jpg");
                //{
                try
                {
                    // write the new file to disk
                    using (FileStream fs = new FileStream(path, FileMode.Create))
                    {
                        enc.Save(fs);
                    }
                    this.StatusText2 = string.Format(CultureInfo.CurrentCulture, Properties.Resources.SavedScreenshotStatusTextFormat, path);
                }

                catch (IOException)
                {
                    this.StatusText2 = string.Format(CultureInfo.CurrentCulture, Properties.Resources.FailedScreenshotStatusTextFormat, path);
                }
            }
            */


                string path = Path.Combine(scp.folder, scp.filename + ".jpg");
                try
                {
                    File.WriteAllBytes(path, scp.data);
                    this.StatusText2 = string.Format(CultureInfo.CurrentCulture, Properties.Resources.SavedScreenshotStatusTextFormat, path);
                }
                catch (IOException)
                {
                    this.StatusText2 = string.Format(CultureInfo.CurrentCulture, Properties.Resources.FailedScreenshotStatusTextFormat, path);
                }
            }
            if (framenamecnt % 50 == 0 && framenamecnt != 0)
            {
                nowTime = DateTime.Now;
                TimeSpan span = nowTime.Subtract(oldTime);
                s = span.TotalSeconds;
                fps = 50 / s;
                this.StatusText3 = fps.ToString();
            }
                return;

        }


        /// <summary>
        /// Directly accesses the underlying image buffer of the DepthFrame to 
        /// create a displayable bitmap.
        /// This function requires the /unsafe compiler option as we make use of direct
        /// access to the native memory pointed to by the depthFrameData pointer.
        /// </summary>
        /// <param name="depthFrameData">Pointer to the DepthFrame image data</param>
        /// <param name="depthFrameDataSize">Size of the DepthFrame image data</param>
        /// <param name="minDepth">The minimum reliable depth value for the frame</param>
        /// <param name="maxDepth">The maximum reliable depth value for the frame</param>
        private unsafe void ProcessDepthFrameData(IntPtr depthFrameData, uint depthFrameDataSize, ushort minDepth, ushort maxDepth)
        {
            // depth frame data is a 16 bit value
            ushort* frameData = (ushort*)depthFrameData;

            // convert depth to a visual representation
            for (int i = 0; i < (int)(depthFrameDataSize / this.depthFrameDescription.BytesPerPixel); ++i)
            {
                // Get the depth for this pixel
                ushort depth = frameData[i];

                // To convert to a byte, we're mapping the depth value to the byte range.depthframenamecnt
                // Values outside the reliable depth range are mapped to 0 (black).
                this.depthPixels[i] = (byte)(depth >= minDepth && depth <= maxDepth ? (depth / MapDepthToByte) : 0);
            }
        }

        /// <summary>
        /// Renders color pixels into the writeableBitmap.and save.
        /// </summary>
        private void RenderDepthPixels()
        {
            //测试帧率
            
            //if (framenamecnt % 50 == 0) {
            //    oldTime =DateTime.Now;
            //}
            this.depthBitmap.WritePixels(
                new Int32Rect(0, 0, this.depthBitmap.PixelWidth, this.depthBitmap.PixelHeight),
                this.depthPixels,
                this.depthBitmap.PixelWidth,
                0);

            //尝试存视频
            //MemoryStream stream = null;
            //stream = new MemoryStream(depthPixels);
            //Bitmap videoDepth = new Bitmap((Image)new Bitmap(stream));
            //VideoFileWriter writer = new VideoFileWriter();
            //writer.Open("F:\\KinectData\\\video\\test.avi", this.depthFrameDescription.Width, this.depthFrameDescription.Height, 25, VideoCodec.MPEG4);
            //writer.WriteVideoFrame(videoDepth);
            //writer.Close();

            BitmapEncoder encoder = new PngBitmapEncoder();
            // create frame from the writable bitmap and add to encoder
            encoder.Frames.Add(BitmapFrame.Create(this.depthBitmap));

            framenamecnt++;
            string framename = framenamecnt.ToString("D5");
            string path = Path.Combine("F:\\KinectData\\depth", "Depth" + framename + ".png");

            // write the new file to disk
            try
            {
                // FileStream is IDisposable
                using (FileStream fs = new FileStream(path, FileMode.Create))
                {
                    encoder.Save(fs);
                }

                this.StatusText1 = string.Format(CultureInfo.CurrentCulture, Properties.Resources.SavedScreenshotStatusTextFormat, path);
            }
            catch (IOException)
            {
                this.StatusText1 = string.Format(CultureInfo.CurrentCulture, Properties.Resources.FailedScreenshotStatusTextFormat, path);
            }
            //if(framenamecnt%50==0&&framenamecnt!=0)
            //{
            //    nowTime = DateTime.Now;
            //    TimeSpan span = nowTime.Subtract(oldTime);
            //    ms = span.TotalSeconds;
            //    fps = 50 / ms;
            //}
            //this.StatusText3 = fps.ToString();
        }

        /// <summary>
        /// Handles the event which the sensor becomes unavailable (E.g. paused, closed, unplugged).
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void Sensor_IsAvailableChanged(object sender, IsAvailableChangedEventArgs e)
        {
            // on failure, set the status text
            this.StatusText1 = this.kinectSensor.IsAvailable ? Properties.Resources.RunningStatusText
                                                            : Properties.Resources.SensorNotAvailableStatusText;
            this.StatusText2 = this.kinectSensor.IsAvailable ? Properties.Resources.RunningStatusText
                                                            : Properties.Resources.SensorNotAvailableStatusText;
        }
    }
}
